/*
 * Copyright 2007-2011 Nicolas Zozol
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.robustaweb.library.commons.util;

import java.util.ArrayList;
import java.util.HashMap;

/**
 * <p>
 * It's just an ArrayList of {@link Couple} objects, somewhat more simple to use than regular lists of Object[2] and allowing generics : <br/>
 * [(3, "Three"),(4, "44"),( 5, "cinquo"), (6, "6")] is a CoupleList&lt;Integer, String>
 *</p>
 * <p>
 * Using Hash or Couples is very convenient in Ajax world.
 * For exemple if you want all the students of your school, you will need id (for further calls) and ScreenName for display .
 * </p>
 * <p>
 * This class is designed to be more simple than fast, but anyway with correct speed.
 * </p>
 * @author Nicolas Zozol for Robusta Web ToolKit & <a href="http://www.edupassion.com">Edupassion.com</a> - nzozol@robustaweb.com
 * @todo 2 : change some method names
 * @see Couple
 */
public class CoupleList<LEFT, RIGHT> extends ArrayList<Couple<LEFT, RIGHT>> {

    @Override
    public Couple<LEFT, RIGHT> get(int index) {
        return super.get(index);
    }



    /**
     * Adds a couple to your list
     * 
     * @param e1 LEFT element
     * @param e2 RIGHT element
     */
    public void addCouple(LEFT e1, RIGHT e2) {
        this.add(new Couple<LEFT, RIGHT>(e1, e2));

    }

    /**
     * returns the LEFT element at the specified index
     * @param index
     * @return the LEFT element at the specified index
     */
    public LEFT getLeftElement(int index) {

        Couple<LEFT, RIGHT> couple = this.get(index);
        return couple.getLeft();
    }

    /**
     * returns the RIGHT element at the specified index
     * @param index
     * @return the RIGHT element at the specified index
     */
    public RIGHT getRightElement(int index) {

        Couple<LEFT, RIGHT> couple = this.get(index);
        return couple.getRight();
    }

    /**
     * Returns an ArrayList&lt;LEFT> of LEFT elements
     * @return an ArrayList&lt;LEFT> of LEFT elements
     */
    public ArrayList<LEFT> getAllLeftElements() {
        ArrayList<LEFT> result = new ArrayList<LEFT>();
        for (int i = 0; i < this.size(); i++) {
            result.add(this.getLeftElement(i));
        }
        return result;
    }

    /**
     * Returns an ArrayList&lt;RIGHT> of RIGHT elements
     * @return an ArrayList&lt;RIGHT> of RIGHT elements
     */
    public ArrayList<RIGHT> getAllRightElements() {
        ArrayList<RIGHT> result = new ArrayList<RIGHT>();
        for (int i = 0; i < this.size(); i++) {
            result.add(this.getRightElement(i));
        }
        return result;
    }

    /**
     * <p>Returns all RIGHT elements equals to Object o when o is a LEFT element, and viceversa
     * o can be sometimes on the left, sometime on the right.
     * </p>
     * <p>For exemple <code>findMyLovers(3)</code> in [(3,"Three"),("Not Three", 3),(4,5),(9,10)]
     * will return ["Three", "Not Three"]</p>
     * @param o The object to test
     * @return a list of matching objects
     */
    public ArrayList findMyLovers(Object o) {

        ArrayList result = new ArrayList();


        /* Looking at first side */
        for (int i = 0; i < this.size(); i++) {
            if (getLeftElement(i).equals(o)) {
                result.add(getRightElement(i));
            }
            if (getRightElement(i).equals(o)) {

                result.add(getLeftElement(i));
                
            }
        }
        return result;
    }

    /**
     *
     * <p>Returns the first LEFT object matching the RIGHT object o</p>
     * <p>For exemple <code>findMyLovers(3)</code> in [(3,"Three"),("Not Three", 3),(4,5),(9,10)]
     * will return <strong>"Not Three"</strong></p>
     * @return first love of o
     */
    
    public LEFT findMyFirstLoveInLeft(RIGHT o) {

        for (int i = 0; i < this.size(); i++) {

            if (getRightElement(i).equals(o)) {
                return getLeftElement(i);
            }
        }
        return null;

    }

    /**
     * <p>Returns the first RIGHT object matching the LEFT object o</p>
     * <p>For exemple <code>findMyLovers(3)</code> in [(3,"Three"),("Not Three", 3),(4,5),(9,10)]
     * will return <strong>"Three"</strong></p>
     * @return first love of o
     */
    public RIGHT findMyFirstLoveInRight(LEFT o) {

        for (int i = 0; i < this.size(); i++) {

            if (getLeftElement(i).equals(o)) {
                return getRightElement(i);
            }
        }
        return null;

    }

    /**
     *<p>Returns the first object matching the  object o, whatever if it's on the Right or the Left</p>
     * @return first love of o
     */
    public Object findMyFirstLove(Object o) {

        for (int i = 0; i < this.size(); i++) {
            if (getLeftElement(i).equals(o)) {
                return getRightElement(i);
            }
            if (getRightElement(i).equals(o)) {
                return getLeftElement(i);
            }
        }
        return null;
    }

    /**
     * <p>Sometimes a CoupleList is close of a HashMap. You can get it with this function.
     * Of course <strong>you may loose datas</strong> in your HashMap if a Left element is not Unique.</p>
     * <p>LEFT will be the key, RIGHT the value ; use the getReversedList() function if you wish to switch.</p>
     * @return a HashMap representation of the CoupleList, with possible loss of datas.
     */
    public HashMap<LEFT, RIGHT> getHashMap() {
        HashMap<LEFT, RIGHT> map = new HashMap<LEFT, RIGHT>();
        
        for (int i = 0; i < this.size(); i++) {
            map.put(this.getLeftElement(i), this.getRightElement(i));
        }
        return map;
    }

    /**
     * <p>Using this function on [(3,"Three"),(4, "44"),( 5, "cinquo"), (6, "6")] will return [("Three", 3),("44", 4),( "cinquo", 5), ("6", 6)]</p>
     * <p>You can't be confused if you use Generics.</p>
     * @return a new list, but with inverted Couples.
     */
    public CoupleList<RIGHT, LEFT> reverse() {
        CoupleList<RIGHT, LEFT> result = new CoupleList<RIGHT, LEFT>();

        for (int i = 0; i < this.size(); i++) {
            result.addCouple(this.getRightElement(i), this.getLeftElement(i));
        }
        return result;


    }

    /**
     * <p>Returns the Couples where the Object o is on the LEFT <strong>OR</strong> the RIGHT.</p>
     * <p>It returns an empty list if nothing is found. It does NOT return null.</p>
     * @param o Object to be found
     * @return couples containing o in LEFT <strong>or</strong> RIGHT
     */
    public CoupleList<LEFT, RIGHT> findWhere(Object o) {

        CoupleList<LEFT, RIGHT> list = new CoupleList();
        for (int i = 0; i < this.size(); i++) {
            if (this.getLeftElement(i).equals(o) || this.getRightElement(i).equals(o)) {
                list.add(this.get(i));
            }
        }
        return list;
    }

    /**
     * <p>Returns the Couples where the <strong>left</strong> is on the LEFT <strong>AND right</strong>  is on the RIGHT.</p>
     * Returns an EMPTY coupleList when can't find the couple, and nerver a NULL coupleList
     * @param left  LEFT object wanted
     * @param right RIGHT object wanted
     * @return couples containing left in LEFT <strong>and</strong> right in RIGHT
     */
    public CoupleList<LEFT, RIGHT> findWhere(LEFT left, RIGHT right) {

        CoupleList<LEFT, RIGHT> list = new CoupleList();
        for (int i = 0; i < this.size(); i++) {
            if (this.getLeftElement(i).equals(left) && this.getRightElement(i).equals(right)) {
                list.add(this.get(i));
            }
        }
        return list;
    }

    /**
     * <p>This function removes all couples (left, right) from the list.</p>
     * <p>Only couples containg left <strong>AND</strong> right elements will be removed.</p>
     * @param left LEFT element to test
     * @param right RIGHT element to test
     */
    public void removeWhere(LEFT left, RIGHT right) {
        CoupleList<LEFT, RIGHT> listToRemove = new CoupleList();
        for (int i = 0; i < this.size(); i++) {
            if (this.getLeftElement(i).equals(left) && this.getRightElement(i).equals(right)) {
                listToRemove.add(this.get(i));
            }
        }

        /* Maintenant on enlève les éléments trouvés */
        for (int i = 0; i < listToRemove.size(); i++) {
            this.remove(listToRemove.get(i));
        }
    }

    /**
     * removes all couples where o is in RIGHT or LEFT
     * @param o
     */
    public void removeWhere(Object o) {

        ArrayList v = this.findWhere(o);
        this.removeAll(v);

        
    }

    /**
     * Easily builds a CoupleList : CoupleList.builder(3, "Three", 4, "44", 5, "cinquo", 6, 6)
     * will create the CoupleList :[(3, "Three"),(4, "44"),( 5, "cinquo"), (6, 6)]
     * @throws IllegalArgumentException if number of params is not pair.
     */
    public static <L,R>  CoupleList build(Object... x) {

        CoupleList<L,R> cpList = new CoupleList<L,R>();
        int size = x.length;

        if (size == 0) {
            return cpList;
        }
        if (size % 2 != 0) {
            throw new IllegalArgumentException("Var-args must be pair");
        }

        int index = 0;
        while (index < size / 2) {
            cpList.addCouple((L)x[2 * index], (R)x[2 * index + 1]);
            index++;
        }


        return cpList;
    }

    /**
     * Returns all elements, couple by couple,; left then right.<br/>
     * For exemple ( (1,A), (2,B), (3,B) ) will return [1, A, 2, B, 3, B]
     * @return
     */
    public Object [] flat(){
        ArrayList<Object> array = new ArrayList<Object>();
        for (Couple c : this){
            array.add(c.getLeft());
            array.add(c.getRight());
        }
        return  array.toArray();
    }


    /**
     * Returns a CoupleList where every objects are toString() values of initial objects
     * @return a stringified CoupleList
     */
    public CoupleList<String, String> stringify(){
        CoupleList<String, String> result = new CoupleList<String, String>();

        for (Couple c : this){
            result.addCouple(c.getLeft().toString(), c.getRight().toString());
        }
        return result;
    }


    /**
     * Considers the CoupleList as a Name/Value list. Returns null if the name is not found
     * @param name
     * @param ignoreCase
     * @return
     */
    public RIGHT getValue(String name, boolean ignoreCase){
        
        for (Couple<LEFT, RIGHT> c : this){
            String val = c.getLeft().toString();
            if (ignoreCase){
                if (val.equalsIgnoreCase(name)){
                    return c.getRight();
                }
            }else{
                if (val.equals(name)){
                    return c.getRight();
                }
            }
        }
        return null;

    }

}







